﻿using System;
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using OpenRefineEngine.Operations;

namespace OpenRefineEngine
{
    public class Engine
    {
        private List<Operation> Operations { get; set; }

        public Engine(string operationsJsonFilePath)
        {
            using (TextReader tr = new StreamReader(operationsJsonFilePath))
                Operations = new JsonSerializer { ContractResolver = new CamelCasePropertyNamesContractResolver() } //JSON is camelCased
                    .Deserialize<List<Operation>>(new JsonTextReader(tr));
        }

        public Engine(TextReader operationsJsonText)
        {
            Operations = new JsonSerializer { ContractResolver = new CamelCasePropertyNamesContractResolver() } //JSON is camelCased
                .Deserialize<List<Operation>>(new JsonTextReader(operationsJsonText));
        }

        public string ApplyOperations(string data, string matchColumnName)
        {
            //English:
            //TODO: If we perform operations in inverse order we could found sooner the match value
            //TODO: but when we found it we must perform from there the rest of operations in actual order to make all needed operations
            foreach (var operation in Operations) //We apply all the operations
            {
                //if (operation.EngineConfig.Facets.Any()) throw new Exception("Operations to Specified 'Facets' not supported");
                if (operation.EngineConfig !=null && operation.EngineConfig.Mode != "row-based") throw new Exception("Operations to not 'row-based' data not supported");
                switch (operation.OperationKeyName) //We switch between different known operations
                {
                    case "core/column-rename": //We rename the Column to Match (if match the specified Column Name)
                        if (matchColumnName == operation.OldColumnName) matchColumnName = operation.NewColumnName;
                        break;

                    case "core/mass-edit":
                        if (operation.ColumnName == matchColumnName) //We match against the specified ColumnName
                        {
                            foreach (var edit in operation.Edits) //we perform all the edits, usually there is only one per operation.
                            {
                                if (edit.FromBlank && string.IsNullOrEmpty(data)) data = edit.ToValue; //match blank
                                if (edit.FromError) throw new Exception("Operations 'from-error' data not supported");
                                foreach (var replaceFrom in edit.FromValue) if (replaceFrom == data) data = edit.ToValue; //match exact value
                            }
                        }
                        break;

                    case "core/text-transform":
                        if (operation.ColumnName == matchColumnName) //We match against the specified ColumnName
                        {
                            if (operation.Expression.StartsWith("grel:"))
                            {
                                var grelEngine = new GREL.GrelEngine(operation.Expression);
                                if (!(operation.Repeat ?? false)) operation.RepeatCount = 1; //No repeat then repeat (perform) only one time
                                for (var i = 0; i < operation.RepeatCount; i++)
                                {
                                    try
                                    {
                                        data = grelEngine.ApplyExpression(data);
                                    }
                                    catch (Exception)
                                    {
                                        if (operation.OnError != "keep-original") throw; //clearer than water
                                    }
                                }
                            }
                            //TODO: Support other's Expression Languages: Clojure, Jython
                        }
                        break;
                }
            }
            return data;
        }
    }
}
